using System;
using System.Configuration;
using System.Reflection;
using System.Text;
using System.Timers;
using System.Web;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;
using log4net;

namespace Lync.Widget.Library.InstantMessaging
{
	public class UCInstantMessageProvider : IDisposable
	{
		private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		private UserEndpoint _userEndpoint;
		private InstantMessagingCall _instantMessagingCall;
		private InstantMessagingFlow _instantMessagingFlow;
		private Conversation _conversation;
		private string _lastReceivedMessageFromLyncClient;
		private bool _hasSentAcceptationRequest;
		private bool _waitingForAcceptation;
		private bool _activityReceived;
		private bool _chatCloseRequested;


		// If no message has been received from the lync user since the last message or chat startup, 
		// trigger a timeout and close the chat
		private Timer _timeoutTimer;
		private DateTime _sessionStartTime;
		private int _timeoutInSeconds = Convert.ToInt32(ConfigurationManager.AppSettings["ChatTimeoutSeconds"]);

		public string Session { get; set; }
		public string SipUri { get; set; }
		public string Subject { get; set; }
		public string Username { get; set; }
		public string Page { get; set; }
	    public string Title { get; set; }
	    public string Topic { get; set; }

		#region Events

		public event ChatRequestAcceptanceChangedEventHandler ChatRequestAcceptanceChanged;
		public event MessageReceivedFromLyncClientEventHandler ChatMessageReceivedFromLyncCLient;
		public event InstantMessageProviderExceptionOccuredEventHandler InstantMessageProviderExceptionOccured;
		public event NotificationEventHandler Notification;
		public event ChatClosedEventHandler ChatClosed;
		public event ComposingStateChangedEventHandler ComposingStateChanged;


		protected virtual void OnChatRequestAcceptanceChanged(ChatRequestAcceptanceChangedEventArgs e)
		{
			if (ChatRequestAcceptanceChanged != null)
				ChatRequestAcceptanceChanged(this, e);
		}

		protected virtual void OnChatMessageReceivedFromLyncClient(MessageReceivedFromLyncClientEventArgs e)
		{
			if (ChatMessageReceivedFromLyncCLient != null)
				ChatMessageReceivedFromLyncCLient(this, e);
		}

		protected virtual void OnInstantMessageProviderExceptionOccured(InstantMessageProviderExceptionOccuredEventArgs e)
		{
			if (InstantMessageProviderExceptionOccured != null)
				InstantMessageProviderExceptionOccured(this, e);
		}

		protected virtual void OnNotification(NotificationEventArgs e)
		{
			if (Notification != null)
				Notification(this, e);
		}

		protected virtual void OnChatClosed(EventArgs e)
		{
			if (!_activityReceived)
			{
				//No activity received yet from lync user so just tell web user the chat wasnt accepted
				OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(false));
			}
			else
			{
				if (ChatClosed != null)
					ChatClosed(this, e);
			}

			Dispose();
		}

		protected virtual void OnComposingStateChanged(ComposingStateChangedEventArgs e)
		{
			if (ComposingStateChanged != null)
				ComposingStateChanged(this, e);
		}

		#endregion

		public UCInstantMessageProvider(CollaborationPlatform collabPlatform, string session, string sipuri, string username, string page, string title, string topic)
		{
			Session = session;
			SipUri = sipuri;
			Subject = username;
			Username = username;
			Page = page;
		    Title = title;
		    Topic = topic;		    

			//mark that the chat request has not yet been accepted
			_waitingForAcceptation = true;

			//have we received any activity from the lync user?
			_activityReceived = false;

			var userEndpointSettings = new UserEndpointSettings(ConfigurationManager.AppSettings["ChatUserSip"], ConfigurationManager.AppSettings["LyncServer"])
										{
											Credential = new System.Net.NetworkCredential(ConfigurationManager.AppSettings["ChatUserName"], ConfigurationManager.AppSettings["ChatUserPass"], ConfigurationManager.AppSettings["ChatUserDomain"]),
											AutomaticPresencePublicationEnabled = true,
										};

			_userEndpoint = new UserEndpoint(collabPlatform, userEndpointSettings);
			_userEndpoint.BeginEstablish(EndEndpointEstablish, _userEndpoint);

			_sessionStartTime = DateTime.Now;
			_timeoutTimer = new Timer(5000);
			_timeoutTimer.Elapsed += _timeoutTimer_Elapsed;
			_timeoutTimer.Start();
		}

		void _timeoutTimer_Elapsed(object sender, ElapsedEventArgs e)
		{
			if (!_activityReceived && _sessionStartTime.AddSeconds(_timeoutInSeconds) < DateTime.Now)
			{
				SendInstantMessage("The chat session was closed after the timeout of " + _timeoutInSeconds + " seconds had been reached.");
				OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(false));
			}
		}

		private void EndEndpointEstablish(IAsyncResult ar)
		{
			var currentEndpoint = ar.AsyncState as LocalEndpoint;
			try
			{
				currentEndpoint.EndEstablish(ar);

				//Establish call
				var convSettings = new ConversationSettings { Priority = ConversationPriority.Normal, Subject = Subject };
				_conversation = new Conversation(_userEndpoint, convSettings);
				_instantMessagingCall = new InstantMessagingCall(_conversation);
				_instantMessagingCall.InstantMessagingFlowConfigurationRequested += InstantMessagingCall_FlowConfigurationRequested;
				_instantMessagingCall.BeginEstablish(SipUri, null, null, CallEstablishCompleted, _instantMessagingCall);
			}
			catch (AuthenticationException authEx)
			{
				// AuthenticationException will be thrown when the credentials are invalid.
				Log.Error("AuthenticationException", authEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(authEx));

			}
			catch (ConnectionFailureException connFailEx)
			{
				// ConnectionFailureException will be thrown when the endpoint cannot connect to the server, or the credentials are invalid.
				Log.Error("ConnectionFailureException", connFailEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(connFailEx));
			}
			catch (InvalidOperationException iOpEx)
			{
				// InvalidOperationException will be thrown when the endpoint is not in a valid state to connect. To connect, the platform must be started and the Endpoint Idle.
				Log.Error("InvalidOperationException", iOpEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(iOpEx));
			}
		}

		private void CallEstablishCompleted(IAsyncResult result)
		{
			var instantMessagingCall = result.AsyncState as InstantMessagingCall;
			try
			{
				instantMessagingCall.EndEstablish(result);
			}
			catch (AuthenticationException authEx)
			{
				// AuthenticationException will be thrown when the credentials are invalid.
				Log.Error("AuthenticationException", authEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(authEx));
			}
			catch (ConnectionFailureException connFailEx)
			{
				// ConnectionFailureException will be thrown when the endpoint cannot connect to the server, or the credentials are invalid.
				Log.Error("ConnectionFailureException", connFailEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(connFailEx));
			}
			catch (InvalidOperationException iOpEx)
			{
				// InvalidOperationException will be thrown when the endpoint is not in a valid state to connect. To connect, the platform must be started and the Endpoint Idle.
				Log.Error("InvalidOperationException", iOpEx);
				OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(iOpEx));
			}
		}

		private void InstantMessagingCall_FlowConfigurationRequested(object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
		{
			_instantMessagingFlow = e.Flow;

			_instantMessagingFlow.StateChanged += InstantMessagingFlow_StateChanged;
			_instantMessagingFlow.MessageReceived += InstantMessagingFlow_MessageReceived;
			_instantMessagingFlow.RemoteComposingStateChanged += InstantMessagingFlow_RemoteComposingStateChanged;
		}

		void InstantMessagingFlow_RemoteComposingStateChanged(object sender, Microsoft.Rtc.Collaboration.ComposingStateChangedEventArgs composingStateChangedEventArgs)
		{
			if (composingStateChangedEventArgs.ComposingState == ComposingState.Composing)
			{
				_activityReceived = true;
			}

			OnComposingStateChanged(new ComposingStateChangedEventArgs(composingStateChangedEventArgs.ComposingState));
		}

		private void InstantMessagingFlow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
		{
			if (e.State == MediaFlowState.Active)
			{
				OnNotification(new NotificationEventArgs("Chat initialized!"));
				OnNotification(new NotificationEventArgs("Checking availability..."));
				if (!_hasSentAcceptationRequest)
                {
                    var initMessage = Username + " has started a chat with you.";

                    if (!String.IsNullOrEmpty(Page))
                    {
                        Page = HttpUtility.UrlDecode(Page, Encoding.UTF8);
                        Page = HttpUtility.HtmlDecode(Page);
                    }

                    var url = !String.IsNullOrEmpty(Title)
                                    ? string.Format("<a href=\"{0}\">{1}</a>", Page, Title)
                                    : Page;


                    if (!string.IsNullOrEmpty(Page)) initMessage += "<br/>Page: " + url;

                    if (!string.IsNullOrEmpty(Topic)) initMessage += "<br/>Topic: " + Topic;


                    var htmlBytes = Encoding.UTF8.GetBytes(initMessage);

                    _instantMessagingFlow.BeginSendInstantMessage(new System.Net.Mime.ContentType("text/html"),
                                                                   htmlBytes, SendMessageCompleted,
                                                                   _instantMessagingFlow);

                    _hasSentAcceptationRequest = true;
				}
			}
			else
			{
				Log.Info(string.Format("MessagingFlow state changed to {0}, for session {1}", _instantMessagingFlow.State, Session));

				if (e.State == MediaFlowState.Terminated)
				{
					OnChatClosed(new EventArgs());
				}
			}
		}

		public void SendInstantMessage(string message)
		{
			if (message == "event.chatClosed")
			{
				_chatCloseRequested = true;
				SendInstantMessage("Closing chat..");
			}
			else
			{
				if (_instantMessagingFlow.State == MediaFlowState.Active)
				{
					_lastReceivedMessageFromLyncClient = message;
					_instantMessagingFlow.BeginSendInstantMessage(_lastReceivedMessageFromLyncClient, SendMessageCompleted, _instantMessagingFlow);
				}
				else
				{
					Log.Error(string.Format("A message was attempted to be sent while the messagingFlow was in state {0}, for session {1}", _instantMessagingFlow.State, Session));
					OnInstantMessageProviderExceptionOccured(new InstantMessageProviderExceptionOccuredEventArgs(new Exception("The message could not be sent because the MessagingFlow is in state " + _instantMessagingFlow.State)));
				}
			}
		}

		private void InstantMessagingFlow_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
		{
			_activityReceived = true;

			var message = e.TextBody;

			_sessionStartTime = DateTime.Now;

			if (_waitingForAcceptation)
			{
				if (message.ToLowerInvariant() == "accept")
				{
					OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(true));
				}
				else if (message.ToLowerInvariant() == "deny")
				{
					OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(false));
				}
				else
				{
					OnChatRequestAcceptanceChanged(new ChatRequestAcceptanceChangedEventArgs(true));
					OnChatMessageReceivedFromLyncClient(new MessageReceivedFromLyncClientEventArgs(message));
				}
				_waitingForAcceptation = false;
			}
			else
			{
				OnChatMessageReceivedFromLyncClient(new MessageReceivedFromLyncClientEventArgs(message));
			}
		}

		private void SendMessageCompleted(IAsyncResult result)
		{
			_instantMessagingFlow.EndSendInstantMessage(result);

			_instantMessagingFlow.LocalComposingState = ComposingState.Idle;

			if (_chatCloseRequested)
			{
				OnChatClosed(new EventArgs());
			}
		}

		private void InstantMessagingCall_Terminated(IAsyncResult ar)
		{
			_instantMessagingCall.EndTerminate(ar);
		}

		public void Dispose()
		{
			_timeoutTimer.Stop();
			_timeoutTimer.Dispose();
			_instantMessagingCall.BeginTerminate(InstantMessagingCall_Terminated, _instantMessagingCall);
		}
	}
}